/*-
 * Copyright (c) 2011,2012 Kai Wang
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "ld.h"
#include "ld_arch.h"
#include "ld_script.h"
#include "ld_exp.h"
#include "ld_layout.h"

ELFTC_VCSID("$Id: ld_exp.c 2526 2012-07-17 17:43:30Z kaiwang27 $");

/*
 * Support routines for ldscript expression.
 */

static struct ld_exp *_alloc_exp(struct ld *ld);
static int64_t _assignment(struct ld *ld, struct ld_exp *le);
static int64_t _func_addr(struct ld *ld, struct ld_exp *le);
static int64_t _func_align(struct ld *ld, struct ld_exp *le);
static int64_t _func_alignof(struct ld *ld, struct ld_exp *le);
static int64_t _func_data_segment_align(struct ld *ld, struct ld_exp *le);
static int64_t _func_data_segment_end(struct ld *ld, struct ld_exp *le);
static int64_t _func_data_segment_relro_end(struct ld *ld, struct ld_exp *le);
static int64_t _func_defined(struct ld *ld, struct ld_exp *le);
static int64_t _func_length(struct ld *ld, struct ld_exp *le);
static int64_t _func_loadaddr(struct ld *ld, struct ld_exp *le);
static int64_t _func_max(struct ld *ld, struct ld_exp *le);
static int64_t _func_min(struct ld *ld, struct ld_exp *le);
static int64_t _func_next(struct ld *ld, struct ld_exp *le);
static int64_t _func_origin(struct ld *ld, struct ld_exp *le);
static int64_t _func_segment_start(struct ld *ld, struct ld_exp *le);
static int64_t _func_sizeof(struct ld *ld, struct ld_exp *le);
static int64_t _func_sizeof_headers(struct ld *ld);
static int64_t _symbol_val(struct ld *ld, char *name);
static int64_t _symbolic_constant(struct ld *ld, const char *name);

#define	_EXP_EVAL(x) ld_exp_eval(ld, (x))
#define	_EXP_DUMP(x) ld_exp_dump(ld, (x))

void
ld_exp_free(struct ld_exp *le)
{

	if (le == NULL)
		return;

	ld_exp_free(le->le_e1);
	ld_exp_free(le->le_e2);
	ld_exp_free(le->le_e3);
	if (le->le_assign != NULL)
		ld_script_assign_free(le->le_assign);
	if (le->le_name != NULL)
		free(le->le_name);
	free(le);
}

struct ld_exp *
ld_exp_unary(struct ld *ld, enum ld_exp_op op, struct ld_exp *e1)
{
	struct ld_exp *le;

	le = _alloc_exp(ld);
	le->le_op = op;
	le->le_e1 = e1;

	return (le);
}

struct ld_exp *
ld_exp_binary(struct ld *ld, enum ld_exp_op op, struct ld_exp *e1,
    struct ld_exp *e2)
{
	struct ld_exp *le;

	le = _alloc_exp(ld);
	le->le_op = op;
	le->le_e1 = e1;
	le->le_e2 = e2;

	return (le);
}

struct ld_exp *
ld_exp_trinary(struct ld *ld, struct ld_exp *e1, struct ld_exp *e2,
    struct ld_exp *e3)
{
	struct ld_exp *le;

	le = _alloc_exp(ld);
	le->le_op = LEOP_TRINARY;
	le->le_e1 = e1;
	le->le_e2 = e2;
	le->le_e3 = e3;

	return (le);
}

struct ld_exp *
ld_exp_sizeof_headers(struct ld *ld)
{
	struct ld_exp *le;

	le = _alloc_exp(ld);
	le->le_op = LEOP_SIZEOF_HEADERS;

	return (le);
}

struct ld_exp *
ld_exp_constant(struct ld *ld, int64_t val)
{
	struct ld_exp *le;

	le = _alloc_exp(ld);
	le->le_op = LEOP_CONSTANT;
	le->le_val = val;

	return (le);
}

struct ld_exp *
ld_exp_symbolic_constant(struct ld *ld, const char *name)
{
	struct ld_exp *le;

	le = _alloc_exp(ld);
	le->le_op = LEOP_SYMBOLIC_CONSTANT;
	le->le_name = strdup(name);
	if (le->le_name == NULL)
		ld_fatal_std(ld, "calloc");

	return (le);
}

struct ld_exp *
ld_exp_symbol(struct ld *ld, const char *name)
{
	struct ld_exp *le;

	le = _alloc_exp(ld);
	le->le_op = LEOP_SYMBOL;
	le->le_name = strdup(name);
	if (le->le_name == NULL)
		ld_fatal_std(ld, "calloc");

	return (le);
}

struct ld_exp *
ld_exp_name(struct ld *ld, const char *name)
{
	struct ld_exp *le;

	le = _alloc_exp(ld);
	le->le_op = LEOP_SECTION_NAME;
	le->le_name = strdup(name);
	if (le->le_name == NULL)
		ld_fatal_std(ld, "calloc");

	return (le);
}

struct ld_exp *
ld_exp_assign(struct ld *ld, struct ld_script_assign *assign)
{
	struct ld_exp *le;

	le = _alloc_exp(ld);
	le->le_op = LEOP_ASSIGN;
	le->le_assign = assign;

	return (le);
}

int64_t
ld_exp_eval(struct ld* ld, struct ld_exp *le)
{

	assert(le != NULL);
	switch (le->le_op) {
	case LEOP_ABS:
		return (abs(_EXP_EVAL(le->le_e1)));
	case LEOP_ADD:
		return (_EXP_EVAL(le->le_e1) + _EXP_EVAL(le->le_e2));
	case LEOP_ADDR:
		return (_func_addr(ld, le));
	case LEOP_ALIGN:
	case LEOP_BLOCK:
		return (_func_align(ld, le));
	case LEOP_ALIGNOF:
		return (_func_alignof(ld, le));
	case LEOP_AND:
		return (_EXP_EVAL(le->le_e1) & _EXP_EVAL(le->le_e2));
	case LEOP_ASSIGN:
		return (_assignment(ld, le));
	case LEOP_CONSTANT:
		return (le->le_val);
	case LEOP_DIV:
		return (_EXP_EVAL(le->le_e1) / _EXP_EVAL(le->le_e2));
	case LEOP_DSA:
		return (_func_data_segment_align(ld, le));
	case LEOP_DSE:
		return (_func_data_segment_end(ld, le));
	case LEOP_DSRE:
		return (_func_data_segment_relro_end(ld, le));
	case LEOP_DEFINED:
		return (_func_defined(ld, le));
	case LEOP_EQUAL:
		return (_EXP_EVAL(le->le_e1) == _EXP_EVAL(le->le_e2));
	case LEOP_GE:
		return (_EXP_EVAL(le->le_e1) >= _EXP_EVAL(le->le_e2));
	case LEOP_GREATER:
		return (_EXP_EVAL(le->le_e1) > _EXP_EVAL(le->le_e2));
	case LEOP_LENGTH:
		return (_func_length(ld, le));
	case LEOP_LOADADDR:
		return (_func_loadaddr(ld, le));
	case LEOP_LOGICAL_AND:
		return (_EXP_EVAL(le->le_e1) && _EXP_EVAL(le->le_e2));
	case LEOP_LOGICAL_OR:
		return (_EXP_EVAL(le->le_e1) || _EXP_EVAL(le->le_e2));
	case LEOP_LSHIFT:
		return (_EXP_EVAL(le->le_e1) << _EXP_EVAL(le->le_e2));
	case LEOP_MAX:
		return (_func_max(ld, le));
	case LEOP_MIN:
		return (_func_min(ld, le));
	case LEOP_MINUS:
		return (-(_EXP_EVAL(le->le_e1)));
	case LEOP_MOD:
		return (_EXP_EVAL(le->le_e1) % _EXP_EVAL(le->le_e2));
	case LEOP_MUL:
		return (_EXP_EVAL(le->le_e1) * _EXP_EVAL(le->le_e2));
	case LEOP_NE:
		return (_EXP_EVAL(le->le_e1) != _EXP_EVAL(le->le_e2));
	case LEOP_NEGATION:
		return (~(_EXP_EVAL(le->le_e1)));
	case LEOP_NEXT:
		return (_func_next(ld, le));
	case LEOP_NOT:
		return (!(_EXP_EVAL(le->le_e1)));
	case LEOP_OR:
		return (_EXP_EVAL(le->le_e1) | _EXP_EVAL(le->le_e2));
	case LEOP_ORIGIN:
		return (_func_origin(ld, le));
	case LEOP_RSHIFT:
		return (_EXP_EVAL(le->le_e1) >> _EXP_EVAL(le->le_e2));
	case LEOP_SEGMENT_START:
		return (_func_segment_start(ld, le));
	case LEOP_SIZEOF:
		return (_func_sizeof(ld, le));
	case LEOP_SIZEOF_HEADERS:
		return (_func_sizeof_headers(ld));
	case LEOP_SUBSTRACT:
		return (_EXP_EVAL(le->le_e1) - _EXP_EVAL(le->le_e2));
	case LEOP_SYMBOL:
		return (_symbol_val(ld, le->le_name));
	case LEOP_SYMBOLIC_CONSTANT:
		return (_symbolic_constant(ld, le->le_name));
	case LEOP_TRINARY:
		return (_EXP_EVAL(le->le_e1) ? _EXP_EVAL(le->le_e2) :
		    _EXP_EVAL(le->le_e3));
	default:
		ld_fatal(ld, "internal: unknown ldscript expression op");
	}

	return (0);
}

void
ld_exp_dump(struct ld *ld, struct ld_exp *le)
{

	assert(le != NULL);

	if (le->le_par)
		printf("(");

	switch (le->le_op) {
	case LEOP_ABS:
		printf("ABS(");
		_EXP_DUMP(le->le_e1);
		printf(")");
		break;
	case LEOP_ADD:
		_EXP_DUMP(le->le_e1);
		printf(" + ");
		_EXP_DUMP(le->le_e2);
		break;
	case LEOP_ADDR:
		printf("ADDR(");
		_EXP_DUMP(le->le_e1);
		printf(")");
		break;
	case LEOP_ALIGN:
	case LEOP_BLOCK:
		printf("ALIGN(");
		_EXP_DUMP(le->le_e1);
		if (le->le_e2 != NULL) {
			printf(", ");
			_EXP_DUMP(le->le_e2);
		}
		printf(")");
		break;
	case LEOP_ALIGNOF:
		printf("ALIGNOF(");
		_EXP_DUMP(le->le_e1);
		printf(")");
		break;
	case LEOP_AND:
		_EXP_DUMP(le->le_e1);
		printf(" & ");
		_EXP_DUMP(le->le_e2);
		break;
	case LEOP_ASSIGN:
		printf("0x%jx", (uintmax_t)  le->le_assign->lda_res);
		break;
	case LEOP_CONSTANT:
		printf("0x%jx", (uintmax_t) le->le_val);
		break;
	case LEOP_DIV:
		_EXP_DUMP(le->le_e1);
		printf(" / ");
		_EXP_DUMP(le->le_e2);
		break;
	case LEOP_DSA:
		printf("DATA_SEGMENT_ALIGN(");
		_EXP_DUMP(le->le_e1);
		printf(", ");
		_EXP_DUMP(le->le_e2);
		printf(")");
		break;
	case LEOP_DSE:
		printf("DATA_SEGMENT_END(");
		_EXP_DUMP(le->le_e1);
		printf(")");
		break;
	case LEOP_DSRE:
		printf("DATA_SEGMENT_RELRO_END(");
		_EXP_DUMP(le->le_e1);
		printf(", ");
		_EXP_DUMP(le->le_e2);
		printf(")");
		break;
	case LEOP_DEFINED:
		printf("DEFINED(");
		_EXP_DUMP(le->le_e1);
		printf(")");
		break;
	case LEOP_EQUAL:
		_EXP_DUMP(le->le_e1);
		printf(" == ");
		_EXP_DUMP(le->le_e2);
		break;
	case LEOP_GE:
		_EXP_DUMP(le->le_e1);
		printf(" >= ");
		_EXP_DUMP(le->le_e2);
		break;
	case LEOP_GREATER:
		_EXP_DUMP(le->le_e1);
		printf(" > ");
		_EXP_DUMP(le->le_e2);
		break;
	case LEOP_LENGTH:
		printf("LENGTH(");
		_EXP_DUMP(le->le_e1);
		printf(")");
		break;
	case LEOP_LOADADDR:
		printf("LOADADDR(");
		_EXP_DUMP(le->le_e1);
		printf(")");
		break;
	case LEOP_LOGICAL_AND:
		_EXP_DUMP(le->le_e1);
		printf(" && ");
		_EXP_DUMP(le->le_e2);
		break;
	case LEOP_LOGICAL_OR:
		_EXP_DUMP(le->le_e1);
		printf(" || ");
		_EXP_DUMP(le->le_e2);
		break;
	case LEOP_LSHIFT:
		_EXP_DUMP(le->le_e1);
		printf(" << ");
		_EXP_DUMP(le->le_e2);
		break;
	case LEOP_MAX:
		printf("MAX(");
		_EXP_DUMP(le->le_e1);
		printf(", ");
		_EXP_DUMP(le->le_e2);
		printf(")");
		break;
	case LEOP_MIN:
		printf("MIN(");
		_EXP_DUMP(le->le_e1);
		printf(", ");
		_EXP_DUMP(le->le_e2);
		printf(")");
		break;
	case LEOP_MINUS:
		printf("-");
		_EXP_DUMP(le->le_e1);
		break;
	case LEOP_MOD:
		_EXP_DUMP(le->le_e1);
		printf(" %% ");
		_EXP_DUMP(le->le_e2);
		break;
	case LEOP_MUL:
		_EXP_DUMP(le->le_e1);
		printf(" * ");
		_EXP_DUMP(le->le_e2);
		break;
	case LEOP_NE:
		_EXP_DUMP(le->le_e1);
		printf(" != ");
		_EXP_DUMP(le->le_e2);
		break;
	case LEOP_NEGATION:
		printf("~");
		_EXP_DUMP(le->le_e1);
		break;
	case LEOP_NEXT:
		printf("NEXT(");
		_EXP_DUMP(le->le_e1);
		printf(")");
		break;
	case LEOP_NOT:
		printf("!");
		_EXP_DUMP(le->le_e1);
		break;
	case LEOP_OR:
		_EXP_DUMP(le->le_e1);
		printf(" | ");
		_EXP_DUMP(le->le_e2);
		break;
	case LEOP_ORIGIN:
		printf("ORIGIN(");
		_EXP_DUMP(le->le_e1);
		printf(")");
		break;
	case LEOP_RSHIFT:
		_EXP_DUMP(le->le_e1);
		printf(" >> ");
		_EXP_DUMP(le->le_e2);
		break;
	case LEOP_SEGMENT_START:
		printf("SEGMENT_START(");
		_EXP_DUMP(le->le_e1);
		printf(", ");
		_EXP_DUMP(le->le_e2);
		printf(")");
		break;
	case LEOP_SIZEOF:
		printf("SIZEOF(");
		_EXP_DUMP(le->le_e1);
		printf(")");
		break;
	case LEOP_SIZEOF_HEADERS:
		printf("SIZEOF_HEADERS");
		break;
	case LEOP_SUBSTRACT:
		_EXP_DUMP(le->le_e1);
		printf(" - ");
		_EXP_DUMP(le->le_e2);
		break;
	case LEOP_SYMBOL:
		printf("%s", le->le_name);
		break;
	case LEOP_SYMBOLIC_CONSTANT:
		printf("0x%jx",
		    (uintmax_t) _symbolic_constant(ld, le->le_name));
		break;
	case LEOP_TRINARY:
		_EXP_DUMP(le->le_e1);
		printf(" ? ");
		_EXP_DUMP(le->le_e2);
		printf(" : ");
		_EXP_DUMP(le->le_e3);
		break;
	default:
		ld_fatal(ld, "internal: unknown ldscript expression op");
	}

	if (le->le_par)
		printf(")");
}

static struct ld_exp *
_alloc_exp(struct ld *ld)
{
	struct ld_exp *le;

	if ((le = calloc(1, sizeof(*le))) == NULL)
		ld_fatal_std(ld, "calloc");

	return (le);
}

static int64_t
_assignment(struct ld *ld, struct ld_exp *le)
{
	struct ld_exp *var;

	assert(le->le_assign != NULL);
	ld_script_process_assign(ld, le->le_assign);
	var = le->le_assign->lda_var;
	return (ld_script_variable_value(ld, var->le_name));
}

static int64_t
_func_addr(struct ld *ld, struct ld_exp *le)
{

	/* TODO */
	(void) ld; (void) le;
	return (0);
}

static int64_t
_func_align(struct ld *ld, struct ld_exp *le)
{
	struct ld_state *ls;

	ls = &ld->ld_state;
	if (le->le_e2 != NULL)
		return (roundup(_EXP_EVAL(le->le_e1), _EXP_EVAL(le->le_e2)));
	else
		return (roundup(ls->ls_loc_counter, _EXP_EVAL(le->le_e1)));
}

static int64_t
_func_alignof(struct ld *ld, struct ld_exp *le)
{

	/* TODO */
	(void) ld; (void) le;
	return (0);
}

static int64_t
_func_data_segment_align(struct ld *ld, struct ld_exp *le)
{
	struct ld_state *ls; 
	uint64_t maxpagesize, commonpagesize;

	/*
	 * TODO: test if align to common page size use less number
	 * of pages.
	 */
	ls = &ld->ld_state;
	maxpagesize = _EXP_EVAL(le->le_e1);
	commonpagesize = _EXP_EVAL(le->le_e2);

	return (roundup(ls->ls_loc_counter, maxpagesize) +
	    (ls->ls_loc_counter & (maxpagesize - 1)));
}

static int64_t
_func_data_segment_end(struct ld *ld, struct ld_exp *le)
{

	return (_EXP_EVAL(le->le_e1));
}

static int64_t
_func_data_segment_relro_end(struct ld *ld, struct ld_exp *le)
{

	/* TODO */
	(void) ld; (void) le;
	return (0);
}

static int64_t
_func_defined(struct ld *ld, struct ld_exp *le)
{

	/* TODO */
	(void) ld; (void) le;
	return (0);
}

static int64_t
_func_length(struct ld *ld, struct ld_exp *le)
{

	/* TODO */
	(void) ld; (void) le;
	return (0);
}

static int64_t
_func_loadaddr(struct ld *ld, struct ld_exp *le)
{

	/* TODO */
	(void) ld; (void) le;
	return (0);
}

static int64_t
_func_max(struct ld *ld, struct ld_exp *le)
{
	uint64_t val1, val2;

	val1 = _EXP_EVAL(le->le_e1);
	val2 = _EXP_EVAL(le->le_e2);

	return (val1 > val2 ? val1 : val2);
}

static int64_t
_func_min(struct ld *ld, struct ld_exp *le)
{
	uint64_t val1, val2;

	val1 = _EXP_EVAL(le->le_e1);
	val2 = _EXP_EVAL(le->le_e2);

	return (val1 > val2 ? val2 : val1);
}

static int64_t
_func_next(struct ld *ld, struct ld_exp *le)
{

	/* TODO */
	(void) ld; (void) le;
	return (0);
}

static int64_t
_func_origin(struct ld *ld, struct ld_exp *le)
{

	/* TODO */
	(void) ld; (void) le;
	return (0);
}

static int64_t
_func_segment_start(struct ld *ld, struct ld_exp *le)
{

	/* TODO */
	(void) ld; (void) le;
	return (0);
}

static int64_t
_func_sizeof(struct ld *ld, struct ld_exp *le)
{

	/* TODO */
	(void) ld; (void) le;
	return (0);
}

static int64_t
_func_sizeof_headers(struct ld *ld)
{

	return (ld_layout_calc_header_size(ld));
}

static int64_t
_symbol_val(struct ld *ld, char *name)
{

	return (ld_script_variable_value(ld, name));
}

static int64_t
_symbolic_constant(struct ld *ld, const char *name)
{

	if (ld->ld_arch == NULL)
		return (0);

	if (strcmp(name, "COMMONPAGESIZE") == 0)
		return (ld->ld_arch->get_common_page_size(ld));
	else if (strcmp(name, "MAXPAGESIZE") == 0)
		return (ld->ld_arch->get_max_page_size(ld));

	return (0);
}
